/***********************************************************************************
**  
* @copyright (c) 2010-2019,  Technology Co., LTD. All Right Reserved.
*
************************************************************************************/
/**
* @file	    duye_file_client.cpp
* @version     
* @brief      
* @author
* @date	    2013-04-05
* @note 
*
* 1. 2013-04-05 Created this file
*/

#include <string>
#include <duye_logger.h>
#include <duye_helper.h>
#include <duye_file_stream.h>
#include <duye_file_client.h>

static const int8* DUYE_LOG_PREFIX = "duye.nets.file.client";
static const int8* DUYE_FT_RES_ALLOWED = "allowed";
//static const int8* DUYE_FT_RES_REFUSED = "refused";
static const int8* DUYE_FT_RES_COMPLETELY = "completely";

namespace duye {
    
FileClient::FileClient() : m_server_port(0), m_is_connected(false) {}
FileClient::FileClient(const std::string& server_ip, const uint16 server_port)
    : m_server_ip(server_ip)
    , m_server_port(server_port)
    , m_is_connected(false) {}

FileClient::~FileClient() {
    disconnect();
}

bool FileClient::test(const std::string& server_ip, 
    const uint16 server_port) {

    m_server_ip = server_ip;
    m_server_port = server_port;
    if (!connect()) { 
        return false;
    }

    disconnect();
    return true;
}

bool FileClient::upload(const FileStat& local_file_stat, 
    const std::string& net_file_path) {

    if (!connect()) {
        return false;
    }

    bool ret = up(local_file_stat, net_file_path);
    disconnect();
    return ret;
}

bool FileClient::upload(const std::string& server_ip, 
    const uint16 server_port, 
    const FileStat& local_file_stat, 
    const std::string& net_file_path) {

    if (server_ip != m_server_ip || server_port != m_server_port) {
        m_server_ip = server_ip;
        m_server_port = server_port;
        if (!connect()) {
            return false;
        }
    }

    bool ret = up(local_file_stat, net_file_path);
    disconnect();
    return ret;
}

bool FileClient::download(const FileStat& net_file_stat, 
    const std::string& local_file_path) {

    if (!connect()) {
        return false;
    }

    bool ret = down(net_file_stat, local_file_path);
    disconnect();
    return ret;
}

bool FileClient::download(const std::string& server_ip, 
    const uint16 server_port, 
    const FileStat& net_file_stat, 
    const std::string& local_file_path) {

    if (server_ip != m_server_ip || server_port != m_server_port) {
        m_server_ip = server_ip;
        m_server_port = server_port;
        if (!connect()) {
            return false;
        }
    }

    bool ret = down(net_file_stat, local_file_path);
    disconnect();
    return ret;
}

bool FileClient::connect() {
    if (m_is_connected) {
        DUYE_WARN("file client had connected");
        return true;
    }

    if (m_server_port == 0) {
        DUYE_ERROR("file server port = 0, don't init");
        return false;
    }

    DUYE_INFO("start connect file server: %s:%d", m_server_ip.c_str(), m_server_port);
    if (!m_tcp_client.connect(m_server_ip, m_server_port, 3000)) {
        DUYE_ERROR("connect to %s:%d failed", m_server_ip.c_str(), m_server_port);
        return false;
    }

    m_is_connected = true;

    return true;
}

bool FileClient::reconnect() {
    disconnect();
    return connect();
}

bool FileClient::disconnect() {
    m_is_connected = false;
    return m_tcp_client.disconnect();
}

bool FileClient::up(const FileStat& local_file_stat, 
    const std::string& net_file_path) {

    if (!duye::FileUtil::isExist(local_file_stat.path.c_str())) {
        DUYE_ERROR("file: '%s' not exist", local_file_stat.path.c_str());
        return false;
    }

    std::string file_header = "{";
    file_header.append(" \"action\": \"upload\"");
    file_header.append(", \"server_file\": \"" + net_file_path + "\"");
    file_header.append(", \"client_file\": \"" + local_file_stat.path + "\"");
    file_header.append(", \"size\": \"" + duye::StrHelper::toStr(local_file_stat.size) + "\"");
    file_header.append(", \"md5sum\": \"" + local_file_stat.md5sum + "\"");
    file_header.append(" }");

    DUYE_INFO("upload file >> \n%s", file_header.c_str());
    if (m_tcp_client.send(file_header.c_str(), file_header.length()) !=  (int32)file_header.length()) {
        DUYE_ERROR("send file: %s header error", local_file_stat.path.c_str());
        return false;
    }  

    // server file header response
    Buffer header_res_buf(1024);
    bool header_flag = false;
    for (uint32 i = 0; i < 5; i++) {
        int32 len = m_tcp_client.recv(header_res_buf);
        if (len <= 0) {
            System::sleep(1);
        } else {
            header_flag = true;
            break;
        }
    }

    if (!header_flag) {
        DUYE_ERROR("recv file: %s header response error", local_file_stat.path.c_str());
        return false;
    }

    DUYE_INFO("recved file server header response: %s", header_res_buf.data());
    if (!StrHelper::begWith(header_res_buf.data(), DUYE_FT_RES_ALLOWED)) {
        DUYE_ERROR("file server refused upload file: %s", local_file_stat.path.c_str());
        return false;
    }

    FileStream file_stream;
    if (!file_stream.initStream(local_file_stat.path)) {
        DUYE_ERROR("open file: '%s' failed", local_file_stat.path.c_str());
        return false;
    }

    int32 once_read_size = 1024;
    int8 buf[1024] = {0};
    int32 offset = 0;

    DUYE_INFO("start upload file: %s", local_file_stat.path.c_str());
    for (;;) {
        int32 read_size = file_stream.readStream(buf, once_read_size, offset);
        if (read_size < 0) {
            DUYE_ERROR("read file: '%s' error", local_file_stat.path.c_str());
            return false;
        }

        if (read_size <= 0) {
            DUYE_ERROR("read file: '%s' empty", local_file_stat.path.c_str());
            return false;
        }

        offset += read_size;
        int32 sent_size = m_tcp_client.send(buf, read_size);
        if (sent_size !=  read_size) {
            DUYE_ERROR("send file: '%s' data error", local_file_stat.path.c_str());
            return false;
        }

        // DUYE_INFO("had send size=%d", sent_size);
        if (read_size < once_read_size) {
            break;
        }
    }

    DUYE_INFO("wait completely response for file: %s", local_file_stat.path.c_str());

    // server completely response
    uint32 sec_bytes = 512;
    uint32 sec_t = offset / sec_bytes + 3;
    Buffer completely_buf(1024);
    bool completely_flag = false;
    for (uint32 i = 0; i < sec_t; i++) {
        int32 len = m_tcp_client.recv(completely_buf);
        if (len <= 0) {
            System::sleep(1);
        } else {
            completely_flag = true;
            break;
        }
    }

    if (!completely_flag) {
        DUYE_ERROR("transfer file: %s error: timeout %d(s)", local_file_stat.path.c_str(), sec_t);
        return false;
    }

    DUYE_INFO("recved completely response for file: %s", completely_buf.data());
    if (!StrHelper::begWith(completely_buf.data(), DUYE_FT_RES_COMPLETELY)) {
        DUYE_ERROR("upload file: %s, %s", local_file_stat.path.c_str(), completely_buf.data());
        return false;
    }   

    DUYE_INFO("upload file: '%s' completely", local_file_stat.path.c_str());

    return true;
}

bool FileClient::down(const FileStat& net_file_stat, const std::string& local_file_path) {
    std::string file_header = "{";
    file_header.append("action: download");
    file_header.append(", server_file:" + net_file_stat.path);
    file_header.append(", client_file:" + local_file_path);
    file_header.append(", size:" + net_file_stat.size);
    file_header.append(", md5sum:" + net_file_stat.md5sum);
    file_header.append("}");

    if (m_tcp_client.send(file_header.c_str(), file_header.length()) !=  (int32)file_header.length()) {
        DUYE_ERROR("send file: %s header error", net_file_stat.path.c_str());
        return false;
    }    

    // wait server handle
    System::sleep(1);

    uint32 total_recved_size = 0;
    int8 buf[1024] = {0};

    for (;;) {
        int64 recv_size = m_tcp_client.recv(buf, sizeof(buf));
        if (recv_size == 0) {
            DUYE_WARN("disconnect client be server");
            return false;
        } else if (recv_size < 0) {
            DUYE_ERROR("receied data error");
            return false;
        }

        total_recved_size += recv_size;

        if (total_recved_size >= net_file_stat.size) {
            break;
        }
    }

    FileStream file_stream;
    if (!FileUtil::isExist(local_file_path.c_str())) {
        FileUtil::removeFile(local_file_path.c_str());
    }

    if (!file_stream.initStream(local_file_path)) {
        DUYE_ERROR("save file: '%s' failed", local_file_path.c_str());
        return false;
    }

    DUYE_INFO("download file: '%s' completely", local_file_path.c_str());

    return true;
}

}
